home *** CD-ROM | disk | FTP | other *** search
- /*******************************************************************************
- * Module : TESTMALL.C *
- *------------------------------------------------------------------------------*
- * Program : DEBUG UTILITY *
- *------------------------------------------------------------------------------*
- * Author : M.R.Watson. (maff@cix) *
- *------------------------------------------------------------------------------*
- * Version : 1.0 *
- *------------------------------------------------------------------------------*
- * Legal Status : Public Domain. *
- *------------------------------------------------------------------------------*
- * Last Updated : 05/07/92 *
- *==============================================================================*
- * Contains a set of routines for checking if any malloced blocks have had data *
- * overwritten of the end or the beginning of them. Also checks for illegal *
- * frees and can display lists of unfreed mallocs. *
- ********************************************************************************
-
- Uses
- ----
-
- These functions allow you to do the following:
-
- * Check if any illegal memory writes have been made within a given distance
- of a malloced or calloced buffer. The module name and line number where
- any offending buffers were allocated is displayed, along with the
- whereabouts of call to free.
-
- * Optionally print out all calls to malloc, calloc and free, along with
- the module name and line numbers where they were called.
-
- * Check what outstanding mallocs there are at any time. This is very useful
- for pinpointing memory leaks.
-
- * Checks if any blocks are being freed twice or, equivalently, if a memory
- block not allocated with malloc() or calloc() is being freed.
-
- How it works
- ------------
-
- When you #include "testmall.h", a set of macros replace calls to malloc,
- calloc and free with calls to special debug versions. These versions should
- not be called directly, but only with the macros, which automatically add
- module name and line number parameters to the calls.
-
- The replacement malloc and calloc automatically add a number of bytes to
- either end of the requested malloc. These bytes are filled with a known
- pattern so any changes to them can be detected. This detection is performed
- when the buffers are freed, or whenever chmall() is called with a NULL ptr.
-
- There are three other macros, chmall(), chfree() and testmall(). These
- provide front ends to internal functions.
-
- There are two ordinary functions which you can call - SetOverlap() and
- SetDebugMallocLevel().
-
- NOTE: The debug level must be one or more to detect any problems! A value of
- zero effectively disable memory allocation checking.
-
- Macros
- ------
-
- * malloc(): This is a direct replacement for the normal malloc, and no
- special action needs to be taken to use it - just use it
- as malloc in the normal way.
- This will print out a diagnostic if the debug level is three
- or more.
-
- * calloc(): Replaces ordinary calloc. See comments above.
- This will print out a diagnostic if the debug level is three
- or more.
-
- * free(): Replaces ordinary free. See comments above.
- This will print out a diagnostic if the debug level is three
- or more.
- If the block has had any overlap bytes altered, or it wasn't
- allocated with malloc or calloc, or its been freed twice, a
- diagnostic message if displayed - but only if the debug level
- is one or more.
-
- * chmall(x): Checks the pointer 'x' to see if any illegal overwrites have
- taken place within its "overlap" bytes. If so, a message is
- printed.
- This will print out a diagnostic if the debug level is three
- or more.
-
- * chfree(): This will print diagnostics for every unfreed buffer, including
- its size, point of allocation and pointer value.
-
- * testmall(x): Displays the amount of memory apparently available for malloc.
- If 'x' is TRUE, individual block sizes comprising the total
- are displayed.
-
- Functions:
- ----------
-
- * SetOverlap(): This should be called once only at the very start of the
- program to set the size of the overlap buffers. A default
- of 16 is used if this function is not called.
-
- * SetMallocDebugLevel(): Sets the debug level as follows.
-
- 0: No messages printed at all.
- 1: Serious errors only printed.
- 2: chmall(), chfree() and testmall() data printed (plus the above).
- 3: All calls to malloc(), calloc() and free() printed (plus the above).
-
- If you set the debug level to zero, it must be done once only, at the
- very beginning of the program before malloc is called. Thereafter, it
- cannot be called. Otherwise, you can set it to any non-zero value at
- any time to facilitate debugging.
-
- How to use it
- -------------
-
- Using these routines is fairly straightforward:
-
- 1) Every module which needs to have memory allocation checking enabled must
- #include "testmall.h" and be recompiled.
-
- 2) You must compile "TESTMALL.C" and link with it.
-
- 3) You must call the "SetOverlap()" function once only at the beginning
- of main() BEFORE any mallocs take place, unless you are happy with
- the default settings. It is recommended that you use one of the main()
- arguments to set these values, so you can periodically invoke full
- debugging to keep an eye on things, without recompiling.
-
- 4) Put judiciously postioned calls to CheckMalloc(), CheckFree() and
- SetDebugMallocLevel() in the code.
-
- Limitations
- -----------
-
- This set of functions was written before heapwalking functions were commonly
- available in libraries - it therefore makes no use of them.
-
- You can call SetDebugMallocLevel() at any time with a value of one or more.
- You may call it only once at the beginning of a program with a value of
- zero, and thereafter it may not be called with a non-zero value.
-
- This malloc replacement is not really suitable for C++, since it doesn't handle
- "new" and "delete". I have another version for C++, which could be made
- available.
-
- Only malloc(), calloc() and free() are supported. This means that the use of
- functions such as realloc() will confuse the system, and cause it to print
- erroneous reports.
-
- Use of these functions slows down the memory allocation substantially,
- particularly if debug output is enabled.
-
- Since extra memory is allocated for each block, you can run out of memory
- extremely quickly - especially if a large number of nominally small blocks
- are being allocated. The solution, such as it is, is to reduce the overlap
- size somewhat. Ideally, it should be possible to turn on and off malloc
- checking for particular pointers, or perhaps to set a "minimum check" size,
- below which blocks are not checked.
-
- The functions assume that the __FILE__ macro will only generate filenames
- without a path - they must be less than
-
- Not *all* malloc bugs can be caught using these functions; however, by
- changing the overlap size, and calling CheckMalloc() frequently (with a
- NULL pointer parameter - see function listing below) you can usually locate
- the majority of bugs.
-
- It is possible for a pointer to allocated in a module without malloc debugging
- support, yet freed in a module which has got debugging support. This will cause
- the free() to erroneously report that an unmalloced pointer is being freed.
- For this reason, it is recommended that *all* modules which you have access to
- are recompiled, if it is feasible.
-
- Similar problems will occur if you willy-nilly call SetOverlap() at any time.
-
- All diagnostic output is sent to stdout using printf(). This may interfere
- with program output. It would be relatively easy to change all the printfs
- to fprintfs and redirect output to either a file or a printer.
-
- *******************************************************************************/
-
- #if defined (DBG_MALLOC)
- #include <stdio.h>
- #include <malloc.h>
- #include <string.h>
- #include <memory.h>
- #include <stdlib.h>
- #include <stdarg.h>
- #include <alloc.h>
-
- #define PATHLEN 16 // Max length of __FILE__ filenames.
- #define NAMECOUNT 64 // Max. No. of modules calling malloc().
- #define MALLCOUNT 4096 // Maximum number of concurrent mallocs.
-
- static int overlap = 16; // The #extra bytes to either side of malloced blocks.
- // Set with "SetOverlap()", or defaults to 16.
-
- //=============================================================================
- // The static global DebugMalloc is used to determine the level of debugging:
- //----------------------------------------------------------------------------
- // 0: No messages printed at all.
- // 1: Serious errors only printed.
- // 2: chmall(), chfree() and testmall() data printed (plus the above).
- // 3: All calls to malloc(), calloc() and free() printed (plus the above).
- //
- // The value can be changed at any time before the first malloc() takes place
- // by calling "SetDebugMallocLevel()".
- //=============================================================================
-
- static int DebugMalloc = 1;
-
- //=============================================================================
- // To use the malloc debug functions, include the following #defines int the
- // sources that you want to check: (These are in "TESTMALL.H")
- //-----------------------------------------------------------------------------
- // #define malloc(x) mymalloc(x,__FILE__,__LINE__)
- // #define calloc(x,y) mycalloc(x,y,__FILE__,__LINE__)
- // #define free(x) myfree(x,__FILE__,__LINE__)
- // #define chmall(x) CheckMalloc(x,__FILE__,__LINE__)
- // #define chfree() CheckFree(__FILE__,__LINE__)
- // #define testmall(f) TestMall(f,__FILE__,__LINE__)
- //=============================================================================
-
- #ifdef malloc
- #undef malloc
- #endif
-
- #ifdef calloc
- #undef calloc
- #endif
-
- #ifdef free
- #undef free
- #endif
-
-
- static struct mallstr
- {
- int name;
- int line;
- unsigned size;
- void *ptr;
- }
- malldata[MALLCOUNT];
-
- static char mallfiles[NAMECOUNT][PATHLEN];
-
- static int mallcount, namecount;
-
- void CheckMalloc( void *p, char *filename, int linenum );
- void myfree( unsigned char *p, char *filename, int linenum );
- void *mycalloc( size_t size, int count, char *filename, int linenum );
- void *mymalloc( size_t size, char *filename, int linenum );
- void TestMalloc( int blocks, char *filename, int linenum );
- void CheckFree( char *file, int line );
- void mem_report (void);
-
- static void addmall( char *p, unsigned size, char *name, int line );
- static void printmalldata( void *p );
- static void CheckBuffer( unsigned char *p, char *filename, int linenum );
- static void out_log (char *fmt, ...);
-
- /*******************************************************************************
- * void SetOverlap( int over ) *
- *------------------------------------------------------------------------------*
- * Sets the amount over overlap at either ends of malloced buffers. *
- * IMPORTANT: Must be called once only, at the beginning of the program BEFORE *
- * ~~~~~~~~~ an memory allocations have been done. *
- *******************************************************************************/
-
- void SetOverlap( int over )
- {
- overlap = over;
- }
-
-
- /*******************************************************************************
- * void SetDebugMallocLevel( int level ) *
- *------------------------------------------------------------------------------*
- * Sets the malloc debugging level. Value may be: *
- * *
- * 0: No messages printed at all. *
- * 1: Serious errors only printed. *
- * 2: chmall(), chfree() and testmall() data printed (plus the above). *
- * 3: All calls to malloc(), calloc() and free() printed (plus the above). *
- *******************************************************************************/
-
- void SetDebugMallocLevel( int level )
- {
- DebugMalloc = level;
- }
-
-
- /*******************************************************************************
- * void TestMalloc( int blocks, char *filename, int line ) *
- *------------------------------------------------------------------------------*
- * Displays the amount of memory available for mallocing. *
- * If blocks is TRUE, the individual blocks comprising this are displayed. *
- * This should be called via the testmall() macro. *
- *******************************************************************************/
-
- void TestMalloc( int blocks, char *filename, int line )
- {
- static void *p[128];
-
- unsigned size;
- long total;
- int count;
- int num;
-
- if (!DebugMalloc)
- return;
-
- size = 65535U;
- count = 0;
- total = 0L;
-
- while ( size > 16 )
- {
- num = 0;
-
- while ( (p[count]=malloc(size)) != NULL )
- {
- num++;
- count++;
- total += size;
-
- if ( count >= (sizeof(p)/sizeof(p[0])) )
- {
- out_log("Too many blocks in function TestMalloc()!\n");
- exit(3);
- }
- }
-
- if (num&&blocks)
- out_log("%d X %u\n", num, size );
-
- if ( size >= 512 )
- size -= 256;
- else
- size -= 16;
- }
-
- out_log("%ld bytes free at (%s:%d)\n", total, filename, line );
-
- while (count)
- free(p[--count]);
- }
-
-
- /*******************************************************************************
- * void *mymalloc( size_t size, char *filename, int linenum ) *
- *------------------------------------------------------------------------------*
- * Replacement for malloc(), called with the malloc() macro. *
- *******************************************************************************/
-
- void *mymalloc( size_t size, char *filename, int linenum )
- {
- char *p;
-
- if (!DebugMalloc)
- return malloc(size);
-
- p = (char *) malloc(size+overlap*2+sizeof(unsigned));
-
- if ( p == NULL )
- return NULL;
-
- addmall(p,size,filename,linenum);
- *(unsigned*)p = size;
-
- if ( DebugMalloc >= 3 )
- out_log("Malloc %5u %p %12.12s:%5d\n", size, p+(overlap+sizeof(unsigned)), filename, linenum );
-
- memset(p+2,0xAA,overlap);
- memset(p+(overlap+sizeof(unsigned))+size,0xAA,overlap);
- return p+(overlap+sizeof(unsigned));
- }
-
- /*******************************************************************************
- * void *mycalloc( size_t size, int count, char *filename, int linenum ) *
- *------------------------------------------------------------------------------*
- * Replacement for calloc() called by the calloc() macro. *
- *******************************************************************************/
-
- void *mycalloc( size_t size, int count, char *filename, int linenum )
- {
- char *p;
-
- if (!DebugMalloc)
- return calloc(size,count);
-
- size *= (unsigned)count;
- p = (char *) malloc(size+overlap*2+sizeof(unsigned));
-
- if ( p == NULL )
- return NULL;
-
- addmall(p,size,filename,linenum);
- *(unsigned*)p = size;
-
- if ( DebugMalloc >= 3 )
- out_log("Calloc %5u %8p %12.12s:%5d\n", size, p+(overlap+sizeof(unsigned)), filename, linenum );
-
- memset(p+2,0xAA,overlap);
- memset(p+(overlap+sizeof(unsigned))+size,0xAA,overlap);
- memset(p+(overlap+sizeof(unsigned)),0,size);
- return p+(overlap+sizeof(unsigned));
- }
-
-
- /*******************************************************************************
- * void myfree( unsigned char *p, char *filename, int linenum ) *
- *------------------------------------------------------------------------------*
- * Replacement for free() called by the free() macro. *
- *******************************************************************************/
-
- void myfree( unsigned char *p, char *filename, int linenum )
- {
- int i, found;
-
- if ( p == NULL ) // If it's NULL, then we don't need to worry!
- return;
-
- if (!DebugMalloc)
- {
- free(p);
- return;
- }
-
- // First check if this pointer is actually stored in the pointer list...
-
- for ( i = found = 0 ; !found && i<mallcount ; i++ )
- {
- if ( malldata[i].ptr == p )
- {
- found = 1;
- break;
- }
- }
-
- if (!found) // Whoops!
- {
- out_log("-------------------------------------------------------------------------------\n");
- out_log("ERROR: Attempting to free a pointer not in malloc list!\n");
- out_log("Could have been freed twice or not malloced or malloced in a different module.\n");
- out_log("Pointer:%p, at (%s:%d)\n", p, filename, linenum );
- out_log("-------------------------------------------------------------------------------\n");
- return;
- }
-
- CheckMalloc(p,filename,linenum);
-
- if ( DebugMalloc >= 3 )
- {
- out_log
- (
- "Free: %5u %8p %12.12s:%5d <-- %12.12s:%5d\n",
- malldata[i].size,
- p, filename, linenum,
- mallfiles[malldata[i].name],
- malldata[i].line
- );
- }
-
- // OK - we've found it, so remove it from the list...
-
- for ( --mallcount ; i < mallcount ; i++ )
- malldata[i] = malldata[i+1];
-
- free(p-(overlap+sizeof(unsigned)));
- }
-
-
- /*******************************************************************************
- * void CheckMalloc( void *p, char *filename, int linenum ) *
- *------------------------------------------------------------------------------*
- * Checks the given pointer (which must have been malloced with mymalloc()) *
- * for buffer limit overwrite violations. *
- * If 'p' is NULL, all currently malloced blocks are checked. *
- *******************************************************************************/
-
- void CheckMalloc( void *p, char *filename, int linenum )
- {
- int i;
-
- if (!DebugMalloc)
- return;
-
- if (p) // Only want to check this pointer...
- {
- CheckBuffer((unsigned char *) p,filename,linenum);
- return;
- }
-
- for ( i = 0 ; i < mallcount ; i++ )
- CheckBuffer((unsigned char *) malldata[i].ptr, filename, linenum );
- }
-
-
- /*******************************************************************************
- * void CheckFree( char *file, int line ) *
- *------------------------------------------------------------------------------*
- * Displays a list of any mallocs which have not yet been freed. *
- *******************************************************************************/
-
- void CheckFree( char *file, int line )
- {
- int i;
-
- if ( DebugMalloc < 2 )
- return;
-
- if (mallcount==0)
- {
- out_log("No unfreed mallocs at (%s:%d)\n", file, line );
- return;
- }
-
- out_log("==============================================\n");
- out_log("List of unfreed mallocs at (%s:%d)\n", file, line );
- out_log("==============================================\n");
- out_log("| Pointer | Size | File Name | Line |\n");
- out_log("+-----------+-------+--------------+-------+\n");
-
- for ( i = 0 ; i < mallcount ; i++ )
- out_log
- (
- "| %9p | %5u | %-12.12s | %5d |\n",
- malldata[i].ptr,
- malldata[i].size,
- mallfiles[malldata[i].name],
- malldata[i].line
- );
-
- out_log("+-----------+-------+--------------+-------+\n");
- }
-
-
- static void CheckBuffer( unsigned char *p, char *filename, int linenum )
- {
- unsigned len;
- int i;
-
- len = *(unsigned*)(p-(overlap+sizeof(unsigned)));
-
- for ( i = 0 ; i < overlap ; i++ )
- {
- if ( *(p-overlap+i) != 0xAA )
- {
- out_log("-------------------------------------------------------------------------------\n");
- out_log("ERROR at (%s:%d), pointer = %8p\n", filename, linenum, p );
- out_log("Illegal buffer access detected at beginning of buffer!\n");
- printmalldata(p);
- out_log("-------------------------------------------------------------------------------\n");
- break;
- }
- }
-
- for ( i = 0 ; i < overlap ; i++ )
- {
- if ( *(p+i+len) != 0xAA )
- {
- out_log("-------------------------------------------------------------------------------\n");
- out_log("ERROR at (%s:%d), pointer = %8p\n", filename, linenum, p );
- out_log("Illegal buffer access detected at end of buffer!\n");
- printmalldata(p);
- out_log("-------------------------------------------------------------------------------\n");
- break;
- }
- }
- }
-
-
- static void printmalldata( void *p )
- {
- int i;
-
- for ( i = 0 ; i < mallcount ; i++ )
- {
- if ( malldata[i].ptr == p )
- {
- out_log
- (
- "Pointer:%8p, size:%u, malloced at %s:%d\n",
- malldata[i].ptr,
- malldata[i].size,
- mallfiles[malldata[i].name],
- malldata[i].line
- );
-
- return;
- }
- }
-
- out_log("Cannot find any malloc data for pointer %8p\n", p );
- }
-
-
- static void addmall( char *p, unsigned size, char *name, int line )
- {
- int i;
-
- p += (overlap+sizeof(unsigned));
-
- if ( mallcount >= MALLCOUNT )
- {
- out_log("-------------------------------------------------------------------------------\n");
- out_log("ERROR IN ADDMALL! Not enough space to save data!\n");
- out_log("Pointer:%8p, size:%u name:%s, line:%d\n", p, size, name, line );
- out_log("-------------------------------------------------------------------------------\n");
- return;
- }
-
- malldata[mallcount].line = line;
- malldata[mallcount].size = size;
- malldata[mallcount].ptr = p;
-
- // See if name is already defined, and we don't need to re-store it...
-
- for ( i = 0 ; i < namecount ; i++ )
- {
- if ( strcmp( mallfiles[i], name ) == 0 )
- {
- malldata[mallcount++].name = i;
- return;
- }
- }
-
- // Couldn't find name - need to add it to list...
-
- if ( namecount >= NAMECOUNT )
- {
- out_log("-------------------------------------------------------------------------------\n");
- out_log("ERROR IN ADDMALL! Not enough space to save file name!\n");
- out_log("Pointer:%8p, size:%u name:%s, line:%d\n", p, size, name, line );
- out_log("-------------------------------------------------------------------------------\n");
- return;
- }
-
- if (strlen(name)>=PATHLEN)
- {
- out_log("Pathname too long - increase size of PATHLEN in TESTMALL.C");
- name = "!!LONG NAME!!";
- }
-
- malldata[mallcount++].name = namecount;
- strcpy( mallfiles[namecount++], name );
- }
-
-
- /****************************************************************************
- * mem_report *
- * Miscellaneous memory statistics. *
- ****************************************************************************/
-
- void mem_report (void)
- {
- out_log ("farcoreleft = %lu bytes\n", farcoreleft ());
- } /* void mem_report (void) */
-
-
- /****************************************************************************
- * out_log *
- * Dump debug output to out_file. *
- ****************************************************************************/
-
- static void out_log (char *fmt, ...)
- {
- va_list argptr; /* -> variable arguments */
- FILE *out_fp; /* -> output file */
-
- if ((out_fp = fopen ("testmall.out", "a")) != NULL)
- {
- va_start (argptr, fmt);
- (void) vfprintf (out_fp, fmt, argptr);
- va_end (argptr);
- (void) fclose (out_fp);
- }
-
- } /* static void out_log (char *fmt, ...) */
-
- #endif
-